(function($) {
    
	if (typeof($) === 'undefined' || typeof($.ui) === 'undefined') {
		throw 'Requires jQuery and jQuery UI';
	}
	
	if (typeof(jqkit) === 'undefined' || typeof(jqkit.math) === 'undefined' || typeof(jqkit.userAgent) === 'undefined' || typeof(jqkit.css) === 'undefined' || 
        typeof(jqkit.string) === 'undefined' || typeof(jqkit.net) === 'undefined') {
		throw 'Requires jqkit, jqkit.math, jqkit.userAgent, jqkit.css, jqkit.string, and jqkit.net';
	}

    jqkit.components.DataGrid = function(element) {
	    var self = this;
		
		if (jqkit.isDefinedAndNotNull(element) === false) {
			throw 'Html element parameter is not defined';
		}
		
		if (element instanceof $ === false) {
			element = $(element);
		}
		
		if (element.hasClass('vc-dg') === false) {
			throw 'Html element parameter is not a valid data grid component wrapper.';
		}
        
        self.isBlocked_ = false;
        self.wrapper_ = element;
        self.model_ = $.parseJSON(self.wrapper_.children('.vc-dg-model').text());
        self.ajaxRequests_ = [];
        self.refreshDataOnMenuHide_ = false;
        self.loadingTimeout_ = null;

        self.wrapper_.data('jqkit.components.dataGrid', self).attr('data-initialize', 'False');
        self.setupEvents_();
        
        if (self.model_.loadDataAfterInitialRender === false && self.model_.onAfterDataBind) {
            self.wrapper_.queue(function(next) {
                jqkit.executeByName.call(self.wrapper_, self.model_.onAfterDataBind, window);
                next();
            });
        }
        
        if (self.model_.onAfterLoad) {
            self.wrapper_.queue(function(next) {
                jqkit.executeByName.call(self.wrapper_, self.model_.onAfterLoad, window);
                next();
            });
        }

        if (self.model_.loadDataAfterInitialRender === true) {
            // Note: Always request all content (not just data rows) here due to pagination concerns.
            self.wrapper_.queue(function(next) {
                self.refreshData_(1, function() {
                    next();
                });
            });
        } else {
            self.wrapper_.queue(function(next) {
                self.markAsLoading(false);
                next();
            });
        }

        self.wrapper_.queue(function(next) {
            self.updatePersistentStorage_();
            next();
        });
    };

    jqkit.components.DataGrid.prototype.wrapper_;
    
    jqkit.components.DataGrid.prototype.model_;

    jqkit.components.DataGrid.prototype.ajaxRequests_;

    jqkit.components.DataGrid.prototype.refreshDataOnMenuHide_;

    jqkit.components.DataGrid.prototype.isBlocked_;

    jqkit.components.DataGrid.prototype.scrollLeft_;

    jqkit.components.DataGrid.prototype.loadingTimeout_;

    jqkit.components.DataGrid.prototype.lookupColumn_ = function(key) {
        var self = this;
        for (var i = 0, length = self.model_.columns.length; i < length; i++) {
            if (self.model_.columns[i].key === key) {
                return self.model_.columns[i];
            }
        }
        return null;
    };

    jqkit.components.DataGrid.prototype.findOverlay_ = function() {
        return this.wrapper_.children('div.vc-dg-relative').children('div.vc-dg-overlay');
    };

    jqkit.components.DataGrid.prototype.findContent_ = function() {
        return this.wrapper_.children('div.vc-dg-relative').children('div.vc-dg-content');
    };

    jqkit.components.DataGrid.prototype.findHeader_ = function() {
        return this.wrapper_.children('div.vc-dg-relative').children('div.vc-dg-header');
    };

    jqkit.components.DataGrid.prototype.findFooter_ = function() {
        return this.wrapper_.children('div.vc-dg-relative').children('div.vc-dg-footer');
    };

    jqkit.components.DataGrid.prototype.findHeaderTableWrapper_ = function() {
        return this.findContent_().children('div.vc-dg-thWrapper');
    };

    jqkit.components.DataGrid.prototype.findHeaderTable_ = function() {
        return this.findHeaderTableWrapper_().children('div.vc-dg-tHeader');
    };

    jqkit.components.DataGrid.prototype.findHeaderTableCells_ = function() {
        return this.findHeaderTable_().children('div.vc-dg-row').children();
    };

    jqkit.components.DataGrid.prototype.findBodyTableWrapper_ = function() {
        return this.findContent_().children('div.vc-dg-tbWrapper');
    };

    jqkit.components.DataGrid.prototype.findBodyTable_ = function() {
        return this.findBodyTableWrapper_().children('div.vc-dg-tBody');
    };

    jqkit.components.DataGrid.prototype.findVirtualizedScrollingHelper_ = function() {
        return this.findContent_().children('div.vc-dg-vScrollHelper');
    };

    jqkit.components.DataGrid.prototype.findFooterTableWrapper_ = function() {
        return this.findContent_().children('div.vc-dg-tfWrapper');
    };

    jqkit.components.DataGrid.prototype.findFooterTable_ = function() {
        return this.findFooterTableWrapper_().children('div.vc-dg-tFooter');
    };

    jqkit.components.DataGrid.prototype.findMenu_ = function() {
        return this.wrapper_.children('div.vc-dg-menu');
    };

    jqkit.components.DataGrid.prototype.findFilter_ = function() {
        return this.wrapper_.children('div.vc-dg-filter');
    };

    jqkit.components.DataGrid.prototype.findAdditionalMarkup_ = function() {
        return this.wrapper_.children('div.vc-dg-extra');
    };

    jqkit.components.DataGrid.prototype.findFilterTable_ = function() {
        return this.findHeaderTableWrapper_().children('div.vc-dg-tFilter');
    };

    jqkit.components.DataGrid.prototype.setupOverlayEvents_ = function() {
        var self = this;
        self.findOverlay_().click(function(event) {
            event.preventDefault();
            event.stopPropagation();
        });
    };

    jqkit.components.DataGrid.prototype.setupColumnMenuEvents_ = function(opt_thCells) {
        var self = this;
        var thCells = opt_thCells || self.findHeaderTableCells_();
        if (self.model_.enableColumnMenus === true) {
            thCells.filter('div.vc-dg-withMenu').find('div.vc-dg-colMenuHandle').click(function(event) {
                event.stopPropagation();
                self.toggleMenu($(this).closest('div.vc-dg-cell').attr('data-col-key'));
            });

            var menu = self.findMenu_();

            // Menu item sorting.
            menu.find('div.vc-dg-sortMenu').delegate('div.vc-dg-menuItem', 'click', function(event) {
                event.stopPropagation();
                self.sort(self.findMenu_().attr('data-col-key'), ($(this).attr('data-sort-desc') === 'True'));
            });

            // Menu item visibility toggle.
            menu.find('div.vc-dg-visibilityMenu').delegate('div.vc-dg-menuItem', 'click', function(event) {
                event.stopPropagation();
                self.setColumnVisibility($(this).attr('data-col-key'));
            });
        }
        thCells = null;
    };

    jqkit.components.DataGrid.prototype.setupColumnFilteringEvents_ = function() {
        var self = this;

        if (self.model_.enableColumnFiltering === false) {
            return;
        }

        self.wrapper_.undelegate('div.vc-dg-f-icon', 'click').delegate('div.vc-dg-f-icon', 'click', function(event) {
            event.stopPropagation();
            self.toggleFilter($(this).closest('div.vc-dg-cell').attr('data-col-key'));
        });

        self.findFilter_().undelegate('.vc-dg-f-clear', 'click').delegate('.vc-dg-f-clear', 'click', function(event) {
            event.stopPropagation();
            // Clear form fields.
            $(this).closest('.vc-dg-filter').find(':input, select option').not('[type=hidden], :disabled').removeAttr('checked').removeAttr('selected')
                .filter('[type!=checkbox], [type!=radio], select').val('')
                .filter('select').children(':first').attr('selected',true);
            self.hideFilter();
            self.refreshData_();
        }).delegate('.vc-dg-f-apply', 'click', function(event) {
            event.stopPropagation();
            self.hideFilter();
            self.refreshData_();
        });
    };

    jqkit.components.DataGrid.prototype.setupColumnSortingEvents_ = function(opt_thCells) {
        var self = this;
        var thCells = opt_thCells || self.findHeaderTableCells_();
        if (self.model_.enableColumnSorting === true) {
            thCells.filter('div.vc-dg-sortable').click(function(event) {
                event.stopPropagation();
                self.sort(this.getAttribute('data-col-key'));
            });
        }
        thCells = null;
    };

    jqkit.components.DataGrid.prototype.setupColumnResizingEvents_ = function(opt_thCells) {
        var self = this;
        var thCells = opt_thCells || self.findHeaderTableCells_();
        if (self.model_.enableColumnResizing === true) {
            thCells.filter('div.vc-dg-resizable').find('div.vc-dg-colResizeHandle').click(function(event) {
                event.stopPropagation();
            }).draggable({
                axis: 'x',
                helper: function(event) {
                    return $('<div style="height:100%;min-width:1px;position:absolute;top:0px;left:0px;" class="vc-dg-colResizeHelper"></div>').appendTo(self.wrapper_.children('div.vc-dg-relative'));
                },
                stop: function(event, ui) {
                    var cell = $(this).closest('div.vc-dg-cell');
                    var newWidth = (cell.innerWidth() + (ui.position.left - ui.originalPosition.left));
                    self.resizeColumn(cell.attr('data-col-key'), newWidth);
                }
            });
        }
        thCells = null;
    };

    jqkit.components.DataGrid.prototype.setupColumnReorderingEvents_ = function(opt_thCells) {
        var self = this;
        var thCells = opt_thCells || self.findHeaderTableCells_();
        if (self.model_.enableColumnReordering === true) {
            thCells.draggable({
                axis: 'x',
                helper: function(event) {
                    return $('<div style="height:100%;;min-width:1px;position:absolute;top:0px;left:0px;" class="vc-dg-colReorderHelper"></div>').appendTo(self.wrapper_.children('div.vc-dg-relative'));
                },
                start: function(event, ui) {
                    $(this).css({ visibility: 'hidden' });
                },
                stop: function(event, ui) {
                    self.markAsLoading(true);

                    var key = $(this).css({ visibility: '' }).attr('data-col-key');
                    var column = self.lookupColumn_(key);
                    var newIndex = 0;
                    
                    self.wrapper_.queue(function(next) {
                        var visibleColumns = [], oldIndex = column.index, totalWidth = 0;

                        // Order visible columns only (and add hidden column style rules).
                        for (var i = 0, length = self.model_.columns.length; i < length; i++) {
                            var col = self.model_.columns[i];
                            if (col.isVisible === true) {
                                visibleColumns[col.index] = col;
                            }
                        }

                        // Lookup new index.
                        for (var i = 0, length = visibleColumns.length; i < length; i++) {
                            var col = visibleColumns[i];
                            if (jqkit.isDefinedAndNotNull(col) === true) {
                                if (ui.position.left >= totalWidth && (ui.position.left < (totalWidth + col.width) || i === (length - 1))) {
                                    newIndex = col.index;
                                    if (newIndex < oldIndex) {
                                        newIndex += 1;
                                    }
                                    break;
                                }
                                totalWidth += col.width;
                            }
                        }

                        visibleColumns = null;
                        next();
                    });
                    
                    self.wrapper_.queue(function(next) {
                        self.setColumnIndex(column.key, newIndex);
                        next();
                    });

                    self.wrapper_.queue(function(next) {
                        self.markAsLoading(false);
                        next();
                    });
                }
            });
        }
        thCells = null;
    };

    jqkit.components.DataGrid.prototype.setupViewportResizingEvents_ = function() {
        var self = this;
        if (self.model_.enableViewPortResizing === true) {
            self.wrapper_.children('div.vc-dg-relative').children('div.vc-dg-vResizeHandle').click(function(event) {
                event.stopPropagation();
            }).draggable({
                axis: 'y',
                helper: function(event) {
                    return $('<div style="width:100%;position:absolute;top:0px;left:0px;" class="vc-dg-vResizeHelper"></div>').appendTo(self.wrapper_);
                },
                stop: function(event, ui) {
                    var newHeight = (self.findBodyTableWrapper_().height() + (ui.position.top - ui.originalPosition.top));
                    self.resizeViewPort(newHeight);
                }
            });
        }
    };

    jqkit.components.DataGrid.prototype.setupHorizontalScrollingEvents_ = function() {
        var self = this;
        self.findBodyTableWrapper_().scroll(self.findHeaderTableWrapper_().add(self.findFooterTableWrapper_()), function(event) {
            if (this.scrollLeft !== self.scrollLeft_) {
                event.data.scrollLeft(this.scrollLeft);
                self.scrollLeft_ = this.scrollLeft;
            }
        });
    };

    jqkit.components.DataGrid.prototype.setupVirtualScrollingEvents_ = function() {
        var self = this;
        if (self.model_.applyVirtualizedScrolling === true) {
            var dataSource = new jqkit.components.DataGrid.PagingDataStore(self.wrapper_.children('div.vc-dg-vScrollCache'), self.model_);
            var scrollHelper = self.findVirtualizedScrollingHelper_();

            self.wrapper_.queue(function(next) {
                self.findBodyTableWrapper_()
			        .bind('vScrollStart', scrollHelper, function(event) {
                        event.data.stop(true, true).fadeIn();
                    })
			        .bind('vScrollStop', scrollHelper, function(event) {
                        event.data.stop(true, true).fadeOut();
                    })
			        .bind('vScroll', scrollHelper, function(event, start, end, last) {
                        if (self.model_.pagingType === 4) {
                            // Virtual paging.
                            event.data.text(start + ' - ' + end + ' of ' + (last));
                        } else {
                            // Buffered paging/scrolling.
                            event.data.text((self.model_.recordStartIndex + start) + ' - ' + (self.model_.recordStartIndex + end));
                        }
                    })
                    .vScroller(dataSource);
                next();
            });
        }
    };

    jqkit.components.DataGrid.prototype.setupPagingEvents_ = function() {
        var self = this;
        if (self.model_.pagingType === 2 || self.model_.pagingType === 3) {
            self.findHeader_().find('div.vc-dg-pager').add(self.findFooter_().find('div.vc-dg-pager'))
                .delegate('.vc-dg-pageLink[data-page-index]', 'click', function(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    self.setPageIndex(parseInt($(this).attr('data-page-index'), 10));
                })
                .delegate('.vc-dg-pageIndex :input', 'change', function(event) {
                    self.setPageIndex(parseInt($(this).val(), 10) - 1);
                })
                .delegate('.vc-dg-pageSize :input', 'change', function(event) {
                    self.setRecordSetSize(parseInt($(this).val(), 10));
                });
        }
    };

    jqkit.components.DataGrid.prototype.setupResetEvents_ = function() {
        var self = this;
        self.findHeader_().find('div.vc-dg-reset').add(self.findFooter_().find('div.vc-dg-reset'))
            .bind('click', function(event) {
                event.preventDefault();
                event.stopPropagation();
                self.reset();
            });
    };

    jqkit.components.DataGrid.prototype.setupSpecialColumnEvents_ = function() {
        var self = this;
        self.findContent_().bind('click', function(event) {
            var wrapper = $(event.target).closest('div.vc-dg-checkbox[data-row-selector], div.vc-dg-crToggle, div.vc-dg-content');

            if (wrapper.length === 0 || wrapper.hasClass('vc-dg-content') === true) {
                return;
            }

            event.preventDefault();
            event.stopPropagation();

            if (wrapper.hasClass('vc-dg-checkbox') === true) {
                if (wrapper.attr('data-bulk-action') === 'True') {
                    self.setBulkRowSelection();
                } else {
                    var row = wrapper.closest('div[data-row-key]');
                    self.setRowSelection(row.attr('data-row-key'), null, row);
                }
            } else if (wrapper.hasClass('vc-dg-crToggle') === true) {
                var row = wrapper.closest('div[data-row-key]');
                self.setChildRowVisibility(row.attr('data-row-key'), null, row);
            }
        });
    };

    jqkit.components.DataGrid.prototype.setupEvents_ = function() {
        var self = this;
        var thCells = self.findHeaderTableCells_();

        self.setupOverlayEvents_();
        self.setupColumnMenuEvents_(thCells);
        self.setupColumnSortingEvents_(thCells);
        self.setupColumnResizingEvents_(thCells);
        self.setupColumnReorderingEvents_(thCells);
        self.setupColumnFilteringEvents_();
        self.setupViewportResizingEvents_();
        self.setupHorizontalScrollingEvents_();
        self.setupPagingEvents_();
        self.setupResetEvents_();
        self.setupSpecialColumnEvents_();

        if (self.model_.loadDataAfterInitialRender === false) {
            self.setupVirtualScrollingEvents_();
        }
        
        thCells = null;
    };

    jqkit.components.DataGrid.prototype.hideMenu = function() {
        var self = this;
        self.findMenu_().hide().removeAttr('data-col-key').children('div[data-col-key]').hide().scrollTop(0);

        if (self.refreshDataOnMenuHide_ === true && self.wrapper_.hasClass('vc-dg-loading') === false && self.isBlocked_ === false) {
            self.refreshData_();
        }
    };

    jqkit.components.DataGrid.prototype.hideFilter = function() {
        var self = this;
        self.findFilter_().hide().removeAttr('data-col-key').children('div[data-col-key]').hide().scrollTop(0);
    };

    jqkit.components.DataGrid.prototype.toggleMenu = function(key, opt_cell) {
        var self = this, column = self.lookupColumn_(key), menu = self.findMenu_(), curMenuKey = menu.attr('data-col-key');
        opt_cell = opt_cell || self.findHeaderTable_().find('div.vc-dg-cell[data-col-key=' + key + ']');

        if (self.model_.enableColumnMenus === false || column.enableMenu === false) {
            return;
        }

        self.hideMenu();

        if (curMenuKey !== key) {
            // Show/hide column specific sections.
            menu.attr('data-col-key', key).children('div[data-col-key=' + key + ']').show();
            
            // Show/hide sorting section.
            menu.find('div.vc-dg-sortMenu').closest('div.vc-dg-menuSection').toggle(column.enableSorting);
            
            // Show/hide visibility section.
            menu.find('div.vc-dg-visibilityMenu').closest('div.vc-dg-menuSection').toggle(self.model_.enableColumnHidingAndShowing);

            // Position and show menu.
            menu.stop(true, true).css({
                position: 'absolute', zIndex: 3, opacity: 0, display: 'block'
            }).position({
                of: opt_cell, my: 'left top', at: 'right bottom', offset: (0 - opt_cell.find('div.vc-dg-colMenuHandle').outerWidth(true) - 1) + ' 0'
            }).fadeTo('fast', 1);
        }
    };

    jqkit.components.DataGrid.prototype.toggleFilter = function(key, opt_cell) {
        var self = this, column = self.lookupColumn_(key), filter = self.findFilter_(), curFilterKey = filter.attr('data-col-key');
        opt_cell = opt_cell || self.findFilterTable_().find('div.vc-dg-cell[data-col-key=' + key + ']');

        if (self.model_.enableColumnFiltering === false || column.enableFiltering === false) {
            return;
        }

        self.hideFilter();

        if (curFilterKey !== key) {
            // Show/hide column specific filter.
            filter.attr('data-col-key', key).children('div[data-col-key=' + key + ']').show();

            // Position and show filter.
            filter.stop(true, true).css({
                position: 'absolute', zIndex: 3, opacity: 0, display: 'block'
            }).position({
                of: opt_cell, my: 'left top', at: 'right bottom', offset: (0 - opt_cell.find('div.vc-dg-f-icon').outerWidth(true) - 1) + ' 0'
            }).fadeTo('fast', 1);
        }
    };

    jqkit.components.DataGrid.prototype.reset = function() {
        var self = this;

        if (self.isBlocked_ === true) {
            return;
        }

        self.model_.sortByColumnExpressionEncoded = '';
        self.model_.sortByColumnExpressionEncrypted = '';
        self.model_.isSortedInDescendingOrder = '';
        self.model_.recordStartIndex = '';
        self.model_.recordSetSize = '';
        self.model_.maximumViewPortHeight = '';
        // Note: The following will cause the request to ignore the persistent settings currently saved.
        self.model_.columns = [{ key: '__reset__' }];
        self.model_.includeInSelectionByDefault = false;
        self.model_.includeInSelection = [];
        self.model_.excludeFromSelection = [];

        self.refreshData_();
    };

    jqkit.components.DataGrid.prototype.setPageIndex = function(pageIndex) {
        var self = this;

        if (self.model_.pagingType !== 2 && self.model_.pagingType !== 3 && self.model_.pagingType !== 4) {
            return;
        }

        if (self.isBlocked_ === true) {
            return;
        }
        
        pageIndex = Math.max(pageIndex, 0);
        self.setRecordStartIndex(pageIndex * self.model_.recordSetSize);
    };

    jqkit.components.DataGrid.prototype.setRecordStartIndex = function(startIndex) {
        var self = this;

        if (self.model_.pagingType !== 2 && self.model_.pagingType !== 3 && self.model_.pagingType !== 4) {
            return;
        }

        if (self.isBlocked_ === true) {
            return;
        }
        
        startIndex = Math.max(startIndex, 0);
        self.model_.recordStartIndex = startIndex;
        self.refreshData_();
    };

    jqkit.components.DataGrid.prototype.setRecordSetSize = function(size) {
        var self = this;

        if (self.model_.pagingType !== 2 && self.model_.pagingType !== 3 && self.model_.pagingType !== 4) {
            return;
        }

        if (self.isBlocked_ === true) {
            return;
        }
        
        size = Math.max(size, 1);
        self.model_.recordSetSize = size;

        // Calculate the new page index to keep user viewing the same start record.
        if (self.model_.pagingType === 2 || self.model_.pagingType === 3) {
            self.model_.recordStartIndex = Math.floor(self.model_.recordStartIndex / size) * self.model_.recordSetSize;
        }

        self.refreshData_();
    };

    jqkit.components.DataGrid.prototype.sort = function(key, opt_descending) {
        var self = this, column = self.lookupColumn_(key);

        if (self.isBlocked_ === true) {
            return;
        }

        if (self.model_.enableColumnSorting === false || column.enableSorting === false) {
            return;
        }

        if (typeof(opt_descending) === 'undefined') {
            opt_descending = (self.model_.sortByColumnExpressionEncoded === column.sortExpressionEncoded && self.model_.isSortedInDescendingOrder === false);
        }

        self.model_.isSortedInDescendingOrder = opt_descending;
        self.model_.sortByColumnExpressionEncoded = column.sortExpressionEncoded;
        self.model_.sortByColumnExpressionEncrypted = column.sortExpressionEncrypted;
        self.model_.recordStartIndex = 0;
        self.refreshData_();
    };

    jqkit.components.DataGrid.prototype.resizeColumn = function(key, width) {
        var self = this, column = self.lookupColumn_(key);

        if (self.isBlocked_ === true) {
            return;
        }

        if (self.model_.enableColumnResizing === false || column.enableResizing === false) {
            return;
        }
        
        width = Math.max(width, self.model_.minimumColumnWidth);
        width = Math.min(width, self.model_.maximumColumnWidth);
        column.width = width;
        self.refreshDisplay_();
    };

    jqkit.components.DataGrid.prototype.resizeViewPort = function(height) {
        var self = this;

        if (self.isBlocked_ === true) {
            return;
        }

        if (self.model_.enableViewPortResizing === false) {
            return;
        }
        
        self.model_.maximumViewPortHeight = Math.max(height, 0);

        if (self.model_.applyVirtualizedScrolling === true) {
            self.findBodyTableWrapper_().vScroller('set', {
                viewportHeight: self.model_.maximumViewPortHeight
            });
        } else {
            self.findBodyTableWrapper_().css({
                minHeight: 'none',
                maxHeight: 'none',
                height: self.model_.maximumViewPortHeight + 'px'
            });
        }

        self.updatePersistentStorage_();
    };

    jqkit.components.DataGrid.prototype.setColumnIndex = function(key, newIndex) {
        var self = this, column = self.lookupColumn_(key), oldIndex = column.index;

        if (self.isBlocked_ === true) {
            return;
        }

        if (self.model_.enableColumnReordering === false) {
            return;
        }
        
        // Update indexes.
        for (var i = 0, length = self.model_.columns.length; i < length; i++) {
            var col = self.model_.columns[i];
            if (col.key === column.key) {
                col.index = newIndex;
            } else if (newIndex > oldIndex && col.index > oldIndex && col.index <= newIndex) {
                col.index -= 1;
            } else if (newIndex < oldIndex && col.index < oldIndex && col.index >= newIndex) {
                col.index += 1;
            }
        }
        
        self.refreshDisplay_();
    };

    jqkit.components.DataGrid.prototype.setColumnVisibility = function(key, opt_hide) {
        var self = this, column = self.lookupColumn_(key);

        if (self.isBlocked_ === true) {
            return;
        }

        if (self.model_.enableColumnHidingAndShowing === false || column.enableHidingAndShowing === false) {
            return;
        }

        if (typeof(opt_hide) === 'undefined') {
            opt_hide = column.isVisible;
        }

        if (opt_hide !== column.isVisible) {
            // Do nothing.
            return;
        }

        if (opt_hide === true) {
            // Minimum number of visible columns check.
            if (self.model_.numberOfVisibleColumns <= self.model_.minimumNumberOfVisibleColumns) {
                return;
            }

            // Minimum number of visible columns with a menu check.
            if (column.enableMenu === true && self.model_.numberOfVisibleColumnsWithMenu <= 1) {
                return;
            }
        } else {
            // Maximum number of visible columns check.
            if (self.model_.numberOfVisibleColumns >= self.model_.maximumNumberOfVisibleColumns) {
                return;
            }
        }

        // Set visibility.
        column.isVisible = !opt_hide;

        // Update counters and html attributes in menu.
        var menuItem = self.findMenu_().find('.vc-dg-visibilityMenu *[data-col-key=' + key + '][data-visible]');
        if (opt_hide === true) {
            self.model_.numberOfVisibleColumns -= 1;
            
            if (column.enableMenu === true) {
                self.model_.numberOfVisibleColumnsWithMenu -= 1;
            }

            menuItem.attr('data-visible', 'False');
        } else {
            self.model_.numberOfVisibleColumns += 1;
            
            if (column.enableMenu === true) {
                self.model_.numberOfVisibleColumnsWithMenu += 1;
            }

            menuItem.attr('data-visible', 'True');

            // Verify column index is not too large for the currently visible column set.
            column.index = Math.min(column.index, (self.model_.numberOfVisibleColumns - 1));
        }

        // Update column indexes.
        for (var i = 0, length = self.model_.columns.length; i < length; i++) {
            var col = self.model_.columns[i];

            if (col.isVisible === false || col.key === column.key) {
                continue;
            }

            if (column.isVisible === true && col.index >= column.index) {
                col.index += 1;
            } else if (column.isVisible === false && col.index > column.index) {
                col.index -= 1;
            }
        }

        if (self.model_.renderHiddenColumns === true) {
            self.refreshDisplay_();
        } else {
            self.refreshDataOnMenuHide_ = true;
        }
    };

    jqkit.components.DataGrid.prototype.refreshDisplay_ = function() {
        var self = this, markAsLoading = (self.wrapper_.hasClass('vc-dg-loading') === false);

        if (markAsLoading === true) {
            self.markAsLoading(true);
        }

        if (self.model_.onBeforeDisplayChange) {
            self.wrapper_.queue(function(next) {
                jqkit.executeByName.call(self.wrapper_, self.model_.onBeforeDisplayChange, window);
                next();
            });
        }

        self.wrapper_.queue(function(next) {
            var totalWidth = 0, rules = [], visibleColumns = [], renderedLeftPositions = [0], currentLeftPositions = [0];
            var rowSelector = 'div.vc-dg_' + self.model_.id + ' > div.vc-dg-relative > div.vc-dg-content > div > div > div.vc-dg-row';
            var vRowSelector = 'div.vc-dg_' + self.model_.id + ' > div.vc-dg-relative > div.vc-dg-content > div.vc-dg-tbWrapper > div > div > div.vc-dg-row';

            // Order visible columns only and build left position array for visible columns in order matching that rendered in dom.
            for (var i = 0, length = self.model_.columns.length; i < length; i++) {
                var column = self.model_.columns[i];
                if (column.isVisible === true) {
                    totalWidth += column.width;
                    renderedLeftPositions.push(totalWidth);
                    visibleColumns[column.index] = column;
                }
            }

            // Clear width for next loop.
            totalWidth = 0;

            // Remove gaps from hidden columns.
            for (var i = 0, length = visibleColumns.length; i < length; i++) {
                var column = visibleColumns[i];
                if (jqkit.isDefinedAndNotNull(column) === true) {
                    totalWidth += column.width;
                    currentLeftPositions.push(totalWidth);
                }
            }

            visibleColumns = null;

            var vrcIndex = 0;
            for (var i = 0, length = self.model_.columns.length; i < length; i++) {
                var column = self.model_.columns[i];
                if (column.isVisible === true) {
                    var columnRules = [];
                    var tbodyCellRules = [];

                    columnRules.push('display: block !important;');
                    columnRules.push('width: ' + column.width + 'px !important;');
                    columnRules.push('left: ' + (currentLeftPositions[column.index] - renderedLeftPositions[vrcIndex])  + 'px !important;');
                    rules.push(rowSelector + ' > div.vc-dg-cell[data-col-key=' + column.key + '] { ' + columnRules.join(' ') + ' } ');

                    if (self.model_.applyVirtualizedScrolling === true) {
                        rules.push(vRowSelector + ' > div.vc-dg-cell[data-col-key=' + column.key + '] { ' + columnRules.join(' ') + ' } ');
                    }

                    vrcIndex += 1;
                } else {
                    rules.push(rowSelector + ' > div.vc-dg-cell[data-col-key=' + column.key + '] { display: none !important; } ');

                    if (self.model_.applyVirtualizedScrolling === true) {
                        rules.push(vRowSelector + ' > div.vc-dg-cell[data-col-key=' + column.key + '] { display: none !important; } ');
                    }
                }
            }

            rules.push('div.vc-dg_' + self.model_.id + ' > div.vc-dg-relative > div.vc-dg-content > div > div { width: ' + (totalWidth + 10000) + 'px !important; } ');
            rules.push('div.vc-dg_' + self.model_.id + ' > div.vc-dg-relative > div.vc-dg-content > div.vc-dg-tbWrapper > div { width: ' + totalWidth + 'px !important; } ');
            rules.push('div.vc-dg_' + self.model_.id + ' > div.vc-dg-relative > div.vc-dg-content > div.vc-dg-tbWrapper > div > div.vc-dg-childRow > div { width: ' + totalWidth + 'px !important; } ');

            if (self.model_.applyVirtualizedScrolling === true) {
                rules.push('div.vc-dg_' + self.model_.id + ' > div.vc-dg-relative > div.vc-dg-content > div.vc-dg-tbWrapper > div > div { width: ' + totalWidth + 'px !important; } ');
            }
            
            jqkit.css.replaceRulesInStyleSheet(self.model_.id + '-styles', rules.join('\n'));

            rules = null;
            next();
        });

        self.wrapper_.queue(function(next) {
            self.updatePersistentStorage_();
            next();
        });

        if (self.model_.onAfterDisplayChange) {
            self.wrapper_.queue(function(next) {
                jqkit.executeByName.call(self.wrapper_, self.model_.onAfterDisplayChange, window);
                next();
            });
        }

        if (markAsLoading === true) {
            self.wrapper_.queue(function(next) {
                self.markAsLoading(false);
                next();
            });
        }
    };

    jqkit.components.DataGrid.prototype.refreshData_ = function(opt_renderMode, opt_callback) {
        var self = this;
        if (self.model_.enableAjaxBinding === true) {
            self.ajaxRefresh_(opt_renderMode, opt_callback);
        } else {
            self.pageLoadRefresh_();
        }
    };

    jqkit.components.DataGrid.prototype.abortRequests_ = function() {
        var self = this;
        for(var i = 0, length = self.ajaxRequests_.length; i < length; i++) {
            self.ajaxRequests_[i].abort();
        }
        self.ajaxRequests_ = [];
        self.wrapper_.clearQueue();
    };

    jqkit.components.DataGrid.prototype.getDataBindingDictionary_ = function(opt_renderMode) {
        var self = this, prefix = self.model_.fieldNamePrefix || '', keyValues = [];

        if (prefix.length > 0 && jqkit.string.endsWith('.') === false) {
            prefix += '.';
        }
        
        keyValues.push({ key: prefix + 'RenderMode', value: (opt_renderMode || 0) });
        keyValues.push({ key: prefix + 'SortByColumnExpression.Encoded', value: (self.model_.sortByColumnExpressionEncoded || '') });
        keyValues.push({ key: prefix + 'SortByColumnExpression.Encrypted', value: (self.model_.sortByColumnExpressionEncrypted || '') });
        keyValues.push({ key: prefix + 'IsSortedInDescendingOrder', value: self.model_.isSortedInDescendingOrder });
        keyValues.push({ key: prefix + 'RecordStartIndex', value: self.model_.recordStartIndex });
        keyValues.push({ key: prefix + 'RecordSetSize', value: (self.model_.recordSetSize || '') });
        keyValues.push({ key: prefix + 'MaximumViewPortHeight', value: (self.model_.maximumViewPortHeight || '') });
        
        keyValues.push({ key: prefix + 'SelectionInput.IncludeInSelectionByDefault', value: (self.model_.includeInSelectionByDefault || false) });
        if (self.model_.includeInSelection !== null) {
            for (var i = 0, length = self.model_.includeInSelection.length; i < length; i++) {
                keyValues.push({ key: prefix + 'SelectionInput.IncludeInSelection[' + i + ']', value: (self.model_.includeInSelection[i] || '') });
            }
        }
        if (self.model_.excludeFromSelection !== null) {
            for (var i = 0, length = self.model_.excludeFromSelection.length; i < length; i++) {
                keyValues.push({ key: prefix + 'SelectionInput.ExcludeFromSelection[' + i + ']', value: (self.model_.excludeFromSelection[i] || '') });
            }
        }

        var filter = self.findFilter_();
        for (var i = 0, length = self.model_.columns.length; i < length; i++) {
            var column = self.model_.columns[i];
            keyValues.push({ key: prefix + 'Columns[' + i + '].Key', value: column.key });
            keyValues.push({ key: prefix + 'Columns[' + i + '].Width', value: column.width });
            keyValues.push({ key: prefix + 'Columns[' + i + '].IsVisible', value: column.isVisible });
            keyValues.push({ key: prefix + 'Columns[' + i + '].Index', value: column.index });
            keyValues.push({ key: prefix + 'Columns[' + i + '].RenderedIndex', value: column.renderedIndex });

            if (self.model_.enableColumnFiltering === true && column.enableFiltering === true) {
                filter.children('div.vc-dg-f-col[data-col-key=' + column.key + ']').find(':input').each(function() {
                    var val = $(this).val();
                    if ($.isArray(val) === true) {
                        // Multi-select or checkbox list.
                        for (var j = 0, jlength = val.length; j < jlength; j++) {
                            keyValues.push({ key: prefix + 'Columns[' + i + '].' + this.name + '[' + j + ']', value: val[j] });
                        }
                    } else {
                        // Single value inputs.
                        keyValues.push({ key: prefix + 'Columns[' + i + '].' + this.name, value: val });
                    }
                });
            }
        }
        filter = null;

        // Find input fields from additional markup.
        self.findAdditionalMarkup_().find(':input').each(function() {
            var input = $(this);
            keyValues.push({ key: input.attr('name'), value: input.val() });
        });

        // TODO: look in additional header and footer rows?

        return keyValues;
    };

    jqkit.components.DataGrid.prototype.updatePersistentStorage_ = function() {
        var self = this, keyValues = self.getDataBindingDictionary_();

        if (self.model_.enablePersistence === false) {
            return;
        }

        if (self.model_.onBeforePersistData) {
            jqkit.executeByName.call(self.wrapper_, self.model_.onBeforePersistData, window, keyValues);
        }

        var ajaxData = { key: self.model_.persistenceKey || '' };
        for (var i = 0, length = keyValues.length; i < length; i++) {
            var keyValue = keyValues[i];
            ajaxData[keyValue.key] = keyValue.value;
        }
        keyValues = null;

        $(window).unbind('unload.vc_dataGrid').bind('unload.vc_dataGrid', ajaxData, function(event) {
            if (self.model_.onPersistDataOverride) {
                jqkit.executeByName.call(self.wrapper_, self.model_.onPersistDataOverride, window, event.data, self.model_.persistenceUrl, self.model_.persistenceKey);
                return;
            }
            $.ajax({
                type: 'post',
                url: self.model_.persistenceUrl,
                data: event.data,
                async: false
            });
        });
        
        ajaxData = null;
    };

    jqkit.components.DataGrid.prototype.pageLoadRefresh_ = function() {
        var self = this, form = '';
        
        self.markAsLoading(true);

        var keyValues = self.getDataBindingDictionary_();

        // Execute optional event handler.
        if (self.model_.onBeforeDataBind) {
            jqkit.executeByName.call(self.wrapper_, self.model_.onBeforeDataBind, window, keyValues);
        }

        form += '<form method="' + self.model_.httpMethod + '" action="' + (self.model_.selectUrl || '') + '" style="display:none;">';
        
        for (var i = 0, length = keyValues.length; i < length; i++) {
            var keyValue = keyValues[i];
            form += '<input type="hidden" name="' + keyValue.key + '" value="' + keyValue.value + '" />';
        }
        
        form += '</form>';

        // Append to body to prevent inserting a form inside another form.
        $(form).appendTo($('body')).trigger('submit');

        form = null;
    };

    jqkit.components.DataGrid.prototype.ajaxRefresh_ = function(opt_renderMode, opt_callback) {
        var self = this;
        opt_renderMode = (opt_renderMode || 1);
        
        // Abort any active ajax requests and clear the queue (in the case that an ajax success/error/complete queued item is being executed).
        self.abortRequests_();
        self.markAsLoading(true);
        self.hideMenu();
        
        var keyValues = self.getDataBindingDictionary_(opt_renderMode);

        // Execute optional event handler.
        if (self.model_.onBeforeDataBind) {
            jqkit.executeByName.call(self.wrapper_, self.model_.onBeforeDataBind, window, keyValues);
        }

        var ajaxData = {};
        for (var i = 0, length = keyValues.length; i < length; i++) {
            var keyValue = keyValues[i];
            ajaxData[keyValue.key] = keyValue.value;
        }

        if (self.model_.onDataBindOverride) {
            jqkit.executeByName.call(self.wrapper_, self.model_.onDataBindOverride, window, ajaxData);
            return;
        }

        keyValues = null;

        self.ajaxRequests_.push($.ajax({
            url: self.model_.selectUrl,
            type: self.model_.httpMethod,
            dataType: 'html',
            data: ajaxData,
            success: function(data, textStatus, jqXHR) {
                self.wrapper_.queue(function(next) {
                    data = $.trim(data);
                    data = data.substring(5, (data.length - 6));
                    setTimeout(next, 100);
                });

                self.wrapper_.queue(function(next) {
                    var currentID = self.model_.id;

                    if (opt_renderMode === 2) {
                        if (self.model_.applyVirtualizedScrolling === true) {
                            self.wrapper_.children('div.vc-dg-vScrollCache').html(data);
                        } else {
                            self.findBodyTable_().html(data);
                        }
                    } else {
                        self.wrapper_.css({ visibility: 'hidden' }).html(data);
                        self.model_ = $.parseJSON(self.wrapper_.children('.vc-dg-model').text());
                        self.model_.id = currentID;
                        self.setupEvents_();
                        self.findContent_().css({ visibility: 'hidden' });
                        self.wrapper_.data('jqkit.components.dataGrid', self).css({ visibility: '' });
                    }

                    data = null;
                    
                    if (jqkit.isDefinedAndNotNull(opt_callback) === true) {
                        opt_callback();
                    }
                    
                    self.refreshDataOnMenuHide_ = false;
                    setTimeout(next, 100);
                });

                self.wrapper_.queue(function(next) {
                    if (self.model_.onAfterDataBind) {
                        jqkit.executeByName.call(self.wrapper_, self.model_.onAfterDataBind, window);
                    }
                    next();
                });

                self.wrapper_.queue(function(next) {
                    self.refreshDisplay_();

                    if (opt_renderMode !== 2) {
                        self.wrapper_.queue(function(innerNext) {
                            self.findContent_().css({ visibility: '' });
                            innerNext();
                        });
                    }

                    setTimeout(next, 100);
                });
            },
            error: function(jqXHR, textStatus, errorThrown) {
                if (textStatus !== 'abort') {
                    self.wrapper_.queue(function(next) {
                        self.findBodyTable_().css({ height: 'auto' }).html(self.wrapper_.children('div.vc-dg-errorLoadingData').clone().show() || '');
                        next();
                    });
                }
            },
            complete: function(jqXHR, textStatus) {
                if (textStatus !== 'abort') {
                    self.wrapper_.queue(function(next) {
                        self.markAsLoading(false);
                        next();
                    });
                }
                ajaxData = null;
            }
        }));
    };

    jqkit.components.DataGrid.prototype.markAsLoading = function(value) {
        var self = this;
        if (self.loadingTimeout_ !== null) {
            clearTimeout(self.loadingTimeout_);
            self.loadingTimeout_ = null;
        }
        if (value === true && self.wrapper_.hasClass('vc-dg-loading') === false) {
            self.wrapper_.addClass('vc-dg-loading');
        } else if (value === false) {
            self.loadingTimeout_ = setTimeout(function() {
                self.wrapper_.removeClass('vc-dg-loading');
            }, 1000);
        }
    };

    jqkit.components.DataGrid.prototype.isRowSelected = function(key) {
        var self = this;
        if (self.model_.includeInSelectionByDefault === true) {
            for (var i = 0, length = self.model_.excludeFromSelection.length; i < length; i++) {
                if (self.model_.excludeFromSelection[i] === key) {
                    return false;
                }
            }
            return true;
        } else if (self.model_.includeInSelection !== null) {
            for (var i = 0, length = self.model_.includeInSelection.length; i < length; i++) {
                if (self.model_.includeInSelection[i] === key) {
                    return true;
                }
            }
        }
        return false;
    };

    jqkit.components.DataGrid.prototype.setRowSelection = function(key, opt_select, opt_row) {
        var self = this, isSelected = self.model_.includeInSelectionByDefault, index = -1;
        opt_row = opt_row || self.findBodyTable_().find('div.vc-dg-row[data-row-key=' + key + ']');

        if (self.model_.applyVirtualizedScrolling === true) {
            opt_row = opt_row.parent();
        }

        // Determine if row is currently selected and set its index if found.
        if (self.model_.includeInSelectionByDefault === true) {
            for (var i = 0, length = self.model_.excludeFromSelection.length; i < length; i++) {
                if (self.model_.excludeFromSelection[i] === key) {
                    index = i;
                    isSelected = false;
                    break;
                }
            }
        } else {
            for (var i = 0, length = self.model_.includeInSelection.length; i < length; i++) {
                if (self.model_.includeInSelection[i] === key) {
                    index = i;
                    isSelected = true;
                    break;
                }
            }
        }
        
        if (jqkit.isDefinedAndNotNull(opt_select) === false) {
            opt_select = !isSelected;
        }
        
        opt_row.removeClass('vc-dg-selRow').removeClass('vc-dg-unselRow');

        if (opt_select === false && isSelected === true) {
            // Unselecting.
            if (self.model_.includeInSelectionByDefault === true) {
                if (self.model_.excludeFromSelection.length === 0) {
                    self.findHeaderTable_().addClass('vc-dg-mixedSelection');
                }

                self.model_.excludeFromSelection.push(key);

                opt_row.addClass('vc-dg-unselRow');
            } else {
                self.model_.includeInSelection.splice(index, 1);

                if (self.model_.includeInSelection.length === 0) {
                    self.findHeaderTable_().removeClass('vc-dg-mixedSelection');
                }
            }
        } else if (opt_select === true && isSelected === false) {
            // Selecting.
            if (self.model_.includeInSelectionByDefault === true) {
                self.model_.excludeFromSelection.splice(index, 1);
                
                if (self.model_.excludeFromSelection.length === 0) {
                    self.findHeaderTable_().removeClass('vc-dg-mixedSelection');
                }
            } else {
                if (self.model_.includeInSelection.length === 0) {
                    self.findHeaderTable_().addClass('vc-dg-mixedSelection');
                }

                self.model_.includeInSelection.push(key);

                opt_row.addClass('vc-dg-selRow');
            }
        } else {
            return;
        }

        if (self.model_.onAfterRowSelectionChange) {
            jqkit.executeByName.call(self.wrapper_, self.model_.onAfterRowSelectionChange, window, key, opt_select);
        }
    };

    jqkit.components.DataGrid.prototype.setBulkRowSelection = function(opt_select) {
        var self = this, tableBody = self.findBodyTable_(), tableHeader = self.findHeaderTable_();
        
        if (jqkit.isDefinedAndNotNull(opt_select) === false) {
            opt_select = !self.model_.includeInSelectionByDefault;
        }
        
        self.model_.includeInSelection = [];
        self.model_.excludeFromSelection = [];
        tableHeader.removeClass('vc-dg-mixedSelection');
        tableBody.children().removeClass('vc-dg-selRow').removeClass('vc-dg-unselRow');

        if (opt_select === false && self.model_.includeInSelectionByDefault === true) {
            self.model_.includeInSelectionByDefault = false;
            tableBody.removeClass('vc-dg-selRowsByDefault');
            tableHeader.removeClass('vc-dg-selRowsByDefault');
        } else if (opt_select === true && self.model_.includeInSelectionByDefault === false) {
            self.model_.includeInSelectionByDefault = true;
            tableBody.addClass('vc-dg-selRowsByDefault');
            tableHeader.addClass('vc-dg-selRowsByDefault');
        } else {
            return;
        }

        if (self.model_.onAfterRowSelectionChange) {
            jqkit.executeByName.call(self.wrapper_, self.model_.onAfterRowSelectionChange, window, null, opt_select);
        }
    };

    jqkit.components.DataGrid.prototype.setChildRowVisibility = function(key, opt_makeVisible, opt_row, opt_childRow) {
        var self = this;
        opt_row = opt_row || self.findBodyTable_().children('div.vc-dg-row[data-row-key=' + key + ']');
        opt_childRow = opt_childRow || opt_row.next('div.vc-dg-childRow');

        if (opt_childRow.length === 0) {
            return;
        }
        
        if (jqkit.isDefinedAndNotNull(opt_makeVisible) === false) {
            opt_makeVisible = !opt_childRow.is(':visible');
        }

        var isVisible = opt_childRow.is(':visible');
        
        if (opt_makeVisible === true && isVisible === false) {
            opt_row.addClass('vc-dg-crExpand');
            opt_childRow.show();
        } else if (opt_makeVisible === false && isVisible === true) {
            opt_row.removeClass('vc-dg-crExpand');
            opt_childRow.hide();
        }
    };

    jqkit.components.DataGrid.areGlobalStylesDefined_ = false;

    jqkit.components.DataGrid.setGlobalStyles_ = function() {
        if (jqkit.components.DataGrid.areGlobalStylesDefined_ === true) {
            return;
        }
        var rules = '';
        rules += '.vc-dg-overlay { display: none !important; }';
        rules += '.vc-dg-loading .vc-dg-overlay { display: block !important; }';
        rules += '.vc-dg-row { overflow: hidden !important; margin: 0px !important; padding: 0px !important; border-width: 0px !important; position: relative !important; clear: both !important; }';
        rules += '.vc-dg-childRow { margin: 0px !important; padding: 0px !important; border-width: 0px !important; position: relative !important; clear: both !important; }';
        rules += '.vc-dg-cell { position: relative !important; top: 0px !important; left: 0px; float: left !important; margin: 0px !important; padding: 0px !important; border-width: 0px !important; }';
        rules += '.vc-dg-iCell { overflow: hidden !important; white-space: nowrap; height: 100%; }';
        rules += '.vc-dg-tHeader, .vc-dg-tFilter, .vc-dg-tBody, .vc-dg-tFooter { display: block !important; }';
        jqkit.css.createStyleSheet(rules, 'jqkit.components.dataGrid');
        jqkit.components.DataGrid.areGlobalStylesDefined_ = true;
        rules = null;
    };

    jqkit.components.DataGrid.initialize = function(opt_selector) {
        if (jqkit.isDefinedAndNotNull(opt_selector) === false) {
            opt_selector = 'div.vc-dg[data-initialize=True]';
        }
        $(opt_selector).each(function() {
            new jqkit.components.DataGrid($(this));
        });
    };

    $(document).ready(function() {
        jqkit.components.DataGrid.setGlobalStyles_();
        jqkit.components.DataGrid.initialize();
    });

    jqkit.components.DataGrid.PagingDataStore = function(element, settings) {
        var self = this;

        function getCachedRange_() {
            var last = Math.max(Math.min(self.startIndex + self.cacheSize - 1, (self.totalCount - 1)), 0);
            if (last === 0) {
                return null;
            }
            return new jqkit.math.Range(self.startIndex, last);
        }
        
        self.grid_ = element.closest('div.vc-dg');
        self.cache_ = element;

        if (settings.pagingType === 4) {
            // Virtual paging.
		    self.startIndex = settings.recordStartIndex;
		    self.cacheSize = settings.recordSetSize || self.cache_.children().length;
            self.totalCount = settings.totalRecordCount;
        } else {
            // Buffered paging/scrolling.
		    self.startIndex = 0;
		    self.totalCount = self.cache_.children().length;
		    self.cacheSize = settings.recordSetSize || self.totalCount;
        }

        self.cachedRange = getCachedRange_();
		self.rowHeight = settings.rowHeight;
        self.contentWrapperSelector = '.vc-dg-tBody';
        self.rowWidth = element.parent().find('.vc-dg-tBody').width();

        self.getCachedRows = function(start, end) {
            var rows = [];
            start = start - self.startIndex;
            end = (end - self.startIndex) + 1;
            self.cache_.children().slice(start, end).each(function() {
                rows.push(this.innerText || this.textContent || this.innerHTML);
            });
            return rows;
		};

		self.updateCachedRow = function(index, markup) {
            var row = self.cache_.children('*[data-row-index=' + index + ']');
            if (row[0].innerText) {
                row[0].innerText = markup;
            } else if (row[0].textContent) {
                row[0].textContent = markup;
            } else {
                // IE8 and below validate setting innerText and innerHTML and will throw runtime errors if anything fails. Only
                // known solution thus far is to just replace the entire script tag (which is very slow).
                row.replaceWith('<script type="text/x-datagrid-row" data-row-index="' + index + '">' + markup + '</script>');
            }
            row = null;
		};
		
        self.updateCache = function(startIndex, callback) {
            var grid = self.grid_.data('jqkit.components.dataGrid');
            grid.model_.recordStartIndex = startIndex;
            grid.refreshData_(2, function() {
                self.startIndex = grid.model_.recordStartIndex;
                callback(getCachedRange_());
            });
		};

        self.abortCacheUpdate = function() {
            var grid = self.cache_.closest('div.vc-dg').data('jqkit.components.dataGrid');
            grid.abortRequests_();
            grid.markAsLoading(false);
        };

        self.onEmptyCache = function() {
            var grid = self.grid_.data('jqkit.components.dataGrid');
            grid.findBodyTable_().html(grid.wrapper_.find('div.vc-dg-emptyDataSource').clone().show());
        };

        self.getRowClassAttribute = function(index) {
            var grid = self.grid_.data('jqkit.components.dataGrid');
            if (grid.model_.pagingType === 3) {
                index += grid.model_.recordStartIndex;
            }
            var key = self.cache_.children('*[data-row-index=' + index + ']').attr('data-row-key');
            var isSelected = grid.isRowSelected(key);
            if (isSelected === true && grid.model_.includeInSelectionByDefault === false) {
                return 'vc-dg-selRow';
            } else if (isSelected === false && grid.model_.includeInSelectionByDefault === true) {
                return 'vc-dg-unselRow';
            }
            return '';
        };
    };

})(jQuery);